{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "36b496da",
   "metadata": {},
   "source": [
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/state-reducers.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239428-lesson-2-state-reducers)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7ae0ff7-497d-4c31-a57a-00fe92799232",
   "metadata": {},
   "source": [
    "# State Reducers \n",
    "\n",
    "## Review\n",
    "\n",
    "We covered a few different ways to define LangGraph state schema, including `TypedDict`, `Pydantic`, or `Dataclasses`.\n",
    " \n",
    "## Goals\n",
    "\n",
    "Now, we're going to dive into reducers, which specify how state updates are performed on specific keys / channels in the state schema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "398c5e8e-641f-4be6-b1e8-7531f86bd2e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langchain_core langgraph"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d5bd534-c5be-48fe-91bc-af39ebee76b7",
   "metadata": {},
   "source": [
    "## Default overwriting state\n",
    "\n",
    "Let's use a `TypedDict` as our state schema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "64e2438c-9353-4256-bc3c-1bb830374c0b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from typing_extensions import TypedDict\n",
    "from IPython.display import Image, display\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "\n",
    "class State(TypedDict):\n",
    "    foo: int\n",
    "\n",
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "69634df1-4f02-446f-b5cf-6a83d1e15e37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'foo': 2}"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\"foo\" : 1})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "775a099c-c41c-412f-8f05-e7436388ae79",
   "metadata": {},
   "source": [
    "Let's look at the state update, `return {\"foo\": state['foo'] + 1}`.\n",
    "\n",
    "As discussed before, by default LangGraph doesn't know the preferred way to update the state.\n",
    " \n",
    "So, it will just overwrite the value of `foo` in `node_1`: \n",
    "\n",
    "```\n",
    "return {\"foo\": state['foo'] + 1}\n",
    "```\n",
    " \n",
    "If we pass `{'foo': 1}` as input, the state returned from the graph is `{'foo': 2}`.\n",
    "\n",
    "## Branching\n",
    "\n",
    "Let's look at a case where our nodes branch."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2b8d6ad4-2991-4325-933d-67057bc150f4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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bX3IIiKAiIgIiICIiAiIgIiICIiAiIgIiICIiDlyeNr5jHWaNuMS1rMbopGHpu0jY/qUNis2/FWIcPm5mMunzKtt55WXR6Niena7fKZ395G47rGufIY+rlactS7Wit1ZRyyQTsD2PHqIPQrbRXFtWrh8P771dCKsDQNev0oZbMY2PrtFDedIxv6hJzgD5hsB6l+fgTY9qs9/+aH7pZ6mHPCv3T/JaM1oRZXpTH5XM6w1tjLOqcwK2Hu14Kpjlh5i19SGV3P8X380jtu7pt+tWz8CbHp1TniP/rQ/dJ9nh9fuktGax2bUNKvJPYmjggjaXPllcGtaB3kk9AFXIQ7WWSq2zG6PBUpBNX7Rpa65MNw2TY/7Ju+7Ser3bOGzWtc/61dB4uOeOe463l54yCx2Tsvna0g7giMnkBB67hu/d16BWNNajD8zfOf0/vsN0cBERedBERAREQEREBERAREQEREBERAREQEREBERAREQEREGe8PyDxJ4o7EkjJ0t/wB31/n+xaEs94f7/CTxQ7v6TpdwG/8AR9fv2/7rQkBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQZ5w+G3Eril5wP8ApOl0A7v9HVu9aGs84e7fCVxS2PXynS36bf8Al1b/AIrQ0BERAREQEREBERAREQEREBERAREQEREBERAREQERVXLarvOyE9HB0a9ySseSzZuTuiijeQCGN5WOL3bEE9wG46k7gbMPDqxJtStrrUipHl3WH6Bg/e5vu08u6w/QMH73N92vRstecd4LLuovVWVt4LTGYyVDHOy96nTmsV8ex/I61Ixhc2IO2PKXEBu+x237iq55d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LPJXg4+HHc4qcdMhgcZw6lin1PeisTyvyw2oQw1o4pHu+IHPsIiQCRuSG7jvXuxeaOE/g/TcH+JmttaYfH4Y3tSyBwgdYlaykwnnkjj2j+S+Tzvm2aPR12Dy7rD9Awfvc33abLXnHeCy7oqR5d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LLuipIzurweuPwhHqFyYb/39l0U9p7UIzQsQzQGnkKrg2xWLucN335XNdsOZjgDsdh3EEAgga68CuiNabW9E3LJhERedBERAREQEREBERAREQEREBERAREQFn2lDu/PE95y9rr/APfs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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "class State(TypedDict):\n",
    "    foo: int\n",
    "\n",
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "def node_2(state):\n",
    "    print(\"---Node 2---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "def node_3(state):\n",
    "    print(\"---Node 3---\")\n",
    "    return {\"foo\": state['foo'] + 1}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "builder.add_node(\"node_2\", node_2)\n",
    "builder.add_node(\"node_3\", node_3)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", \"node_2\")\n",
    "builder.add_edge(\"node_1\", \"node_3\")\n",
    "builder.add_edge(\"node_2\", END)\n",
    "builder.add_edge(\"node_3\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "106729b3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n",
      "---Node 2---\n",
      "---Node 3---\n",
      "InvalidUpdateError occurred: At key 'foo': Can receive only one value per step. Use an Annotated key to handle multiple values.\n"
     ]
    }
   ],
   "source": [
    "from langgraph.errors import InvalidUpdateError\n",
    "try:\n",
    "    graph.invoke({\"foo\" : 1})\n",
    "except InvalidUpdateError as e:\n",
    "    print(f\"InvalidUpdateError occurred: {e}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b9717ccd-3d34-476a-8952-e6a7629ebefe",
   "metadata": {},
   "source": [
    "We see a problem! \n",
    "\n",
    "Node 1 branches to nodes 2 and 3.\n",
    "\n",
    "Nodes 2 and 3 run in parallel, which means they run in the same step of the graph.\n",
    "\n",
    "They both attempt to overwrite the state *within the same step*. \n",
    "\n",
    "This is ambiguous for the graph! Which state should it keep? "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1609cf7-dc47-4926-a154-77904b6cc550",
   "metadata": {},
   "source": [
    "## Reducers\n",
    "\n",
    "[Reducers](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) give us a general way to address this problem.\n",
    "\n",
    "They specify how to perform updates.\n",
    "\n",
    "We can use the `Annotated` type to specify a reducer function. \n",
    "\n",
    "For example, in this case let's append the value returned from each node rather than overwriting them.\n",
    "\n",
    "We just need a reducer that can perform this: `operator.add` is a function from Python's built-in operator module.\n",
    "\n",
    "When `operator.add` is applied to lists, it performs list concatenation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "103d808c-55ec-44f2-a688-7b5e1572875a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from operator import add\n",
    "from typing import Annotated\n",
    "\n",
    "class State(TypedDict):\n",
    "    foo: Annotated[list[int], add]\n",
    "\n",
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": [state['foo'][0] + 1]}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9e68cdff-f6e1-4de5-a7bf-6ca0cfee19bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'foo': [1, 2]}"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\"foo\" : [1]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63fbd6e0-0207-4049-b86d-c006cbba630b",
   "metadata": {},
   "source": [
    "Now, our state key `foo` is a list.\n",
    "\n",
    "This `operator.add` reducer function will append updates from each node to this list. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "768fd0ed-5e24-44a4-b14d-0e299310e105",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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bX3IIiKAiIgIiICIiAiIgIiICIiAiIgIiICIiDlyeNr5jHWaNuMS1rMbopGHpu0jY/qUNis2/FWIcPm5mMunzKtt55WXR6Niena7fKZ395G47rGufIY+rlactS7Wit1ZRyyQTsD2PHqIPQrbRXFtWrh8P771dCKsDQNev0oZbMY2PrtFDedIxv6hJzgD5hsB6l+fgTY9qs9/+aH7pZ6mHPCv3T/JaM1oRZXpTH5XM6w1tjLOqcwK2Hu14Kpjlh5i19SGV3P8X380jtu7pt+tWz8CbHp1TniP/rQ/dJ9nh9fuktGax2bUNKvJPYmjggjaXPllcGtaB3kk9AFXIQ7WWSq2zG6PBUpBNX7Rpa65MNw2TY/7Ju+7Ser3bOGzWtc/61dB4uOeOe463l54yCx2Tsvna0g7giMnkBB67hu/d16BWNNajD8zfOf0/vsN0cBERedBERAREQEREBERAREQEREBERAREQEREBERAREQEREGe8PyDxJ4o7EkjJ0t/wB31/n+xaEs94f7/CTxQ7v6TpdwG/8AR9fv2/7rQkBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQZ5w+G3Eril5wP8ApOl0A7v9HVu9aGs84e7fCVxS2PXynS36bf8Al1b/AIrQ0BERAREQEREBERAREQEREBERAREQEREBERAREQERVXLarvOyE9HB0a9ySseSzZuTuiijeQCGN5WOL3bEE9wG46k7gbMPDqxJtStrrUipHl3WH6Bg/e5vu08u6w/QMH73N92vRstecd4LLuovVWVt4LTGYyVDHOy96nTmsV8ex/I61Ixhc2IO2PKXEBu+x237iq55d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LPJXg4+HHc4qcdMhgcZw6lin1PeisTyvyw2oQw1o4pHu+IHPsIiQCRuSG7jvXuxeaOE/g/TcH+JmttaYfH4Y3tSyBwgdYlaykwnnkjj2j+S+Tzvm2aPR12Dy7rD9Awfvc33abLXnHeCy7oqR5d1h+gYP3ub7tPLusP0DB+9zfdpstecd4LLuipIzurweuPwhHqFyYb/39l0U9p7UIzQsQzQGnkKrg2xWLucN335XNdsOZjgDsdh3EEAgga68CuiNabW9E3LJhERedBERAREQEREBERAREQEREBERAREQFn2lDu/PE95y9rr/APfs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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": [state['foo'][-1] + 1]}\n",
    "\n",
    "def node_2(state):\n",
    "    print(\"---Node 2---\")\n",
    "    return {\"foo\": [state['foo'][-1] + 1]}\n",
    "\n",
    "def node_3(state):\n",
    "    print(\"---Node 3---\")\n",
    "    return {\"foo\": [state['foo'][-1] + 1]}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(State)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "builder.add_node(\"node_2\", node_2)\n",
    "builder.add_node(\"node_3\", node_3)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", \"node_2\")\n",
    "builder.add_edge(\"node_1\", \"node_3\")\n",
    "builder.add_edge(\"node_2\", END)\n",
    "builder.add_edge(\"node_3\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5439baad-5a75-4188-b936-dbe74cdd9078",
   "metadata": {},
   "source": [
    "We can see that updates in nodes 2 and 3 are performed concurrently because they are in the same step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "44598f97-0a59-4ed4-9d9a-e15a98b3d8fb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n",
      "---Node 2---\n",
      "---Node 3---\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'foo': [1, 2, 3, 3]}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.invoke({\"foo\" : [1]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87faaa07-2955-4466-9bca-4b536e05f260",
   "metadata": {},
   "source": [
    "Now, let's see what happens if we pass `None` to `foo`.\n",
    "\n",
    "We see an error because our reducer, `operator.add`, attempts to concatenate `NoneType` pass as input to list in `node_1`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "7f05984b-2bc7-48d1-b070-c8a001a6b59a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    graph.invoke({\"foo\" : None})\n",
    "except TypeError as e:\n",
    "    print(f\"TypeError occurred: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f9d4930-ee8f-4ffc-b9e1-3c910b2e15f6",
   "metadata": {},
   "source": [
    "## Custom Reducers\n",
    "\n",
    "To address cases like this, [we can also define custom reducers](https://langchain-ai.github.io/langgraph/how-tos/subgraph/#custom-reducer-functions-to-manage-state). \n",
    "\n",
    "For example, lets define custom reducer logic to combine lists and handle cases where either or both of the inputs might be `None`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "3314219d-29ff-4b78-b18e-fa9f7878a02f",
   "metadata": {},
   "outputs": [],
   "source": [
    "def reduce_list(left: list | None, right: list | None) -> list:\n",
    "    \"\"\"Safely combine two lists, handling cases where either or both inputs might be None.\n",
    "\n",
    "    Args:\n",
    "        left (list | None): The first list to combine, or None.\n",
    "        right (list | None): The second list to combine, or None.\n",
    "\n",
    "    Returns:\n",
    "        list: A new list containing all elements from both input lists.\n",
    "               If an input is None, it's treated as an empty list.\n",
    "    \"\"\"\n",
    "    if not left:\n",
    "        left = []\n",
    "    if not right:\n",
    "        right = []\n",
    "    return left + right\n",
    "\n",
    "class DefaultState(TypedDict):\n",
    "    foo: Annotated[list[int], add]\n",
    "\n",
    "class CustomReducerState(TypedDict):\n",
    "    foo: Annotated[list[int], reduce_list]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcdea26a-38d0-4faf-9bf6-cd52eb902635",
   "metadata": {},
   "source": [
    "In `node_1`, we append the value 2."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "f5f270db-6eff-47c9-853b-dfb8108ff28c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TypeError occurred: can only concatenate list (not \"NoneType\") to list\n"
     ]
    }
   ],
   "source": [
    "def node_1(state):\n",
    "    print(\"---Node 1---\")\n",
    "    return {\"foo\": [2]}\n",
    "\n",
    "# Build graph\n",
    "builder = StateGraph(DefaultState)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "\n",
    "try:\n",
    "    print(graph.invoke({\"foo\" : None}))\n",
    "except TypeError as e:\n",
    "    print(f\"TypeError occurred: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd21936b-62f1-4311-9ce5-2c7d08aa35bf",
   "metadata": {},
   "source": [
    "Now, try with our custom reducer. We can see that no error is thrown."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "867784bc-796c-4b1e-a4d3-2810395cf5e2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---Node 1---\n",
      "{'foo': [2]}\n"
     ]
    }
   ],
   "source": [
    "# Build graph\n",
    "builder = StateGraph(CustomReducerState)\n",
    "builder.add_node(\"node_1\", node_1)\n",
    "\n",
    "# Logic\n",
    "builder.add_edge(START, \"node_1\")\n",
    "builder.add_edge(\"node_1\", END)\n",
    "\n",
    "# Add\n",
    "graph = builder.compile()\n",
    "\n",
    "# View\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))\n",
    "\n",
    "try:\n",
    "    print(graph.invoke({\"foo\" : None}))\n",
    "except TypeError as e:\n",
    "    print(f\"TypeError occurred: {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7ebc65e-c185-4981-a6e7-20fe37d2f8fe",
   "metadata": {},
   "source": [
    "## Messages\n",
    "\n",
    "In module 1, we showed how to use a built-in reducer, `add_messages`, to handle messages in state.\n",
    "\n",
    "We also showed that [`MessagesState` is a useful shortcut if you want to work with messages](https://langchain-ai.github.io/langgraph/concepts/low_level/#messagesstate). \n",
    "\n",
    "* `MessagesState` has a built-in `messages` key \n",
    "* It also has a built-in `add_messages` reducer for this key\n",
    "\n",
    "These two are equivalent. \n",
    "\n",
    "We'll use the `MessagesState` class via `from langgraph.graph import MessagesState` for brevity.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "901e69e5-c4cb-4d58-82fb-3b7d968758e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "from langgraph.graph import MessagesState\n",
    "from langchain_core.messages import AnyMessage\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "# Define a custom TypedDict that includes a list of messages with add_messages reducer\n",
    "class CustomMessagesState(TypedDict):\n",
    "    messages: Annotated[list[AnyMessage], add_messages]\n",
    "    added_key_1: str\n",
    "    added_key_2: str\n",
    "    # etc\n",
    "\n",
    "# Use MessagesState, which includes the messages key with add_messages reducer\n",
    "class ExtendedMessagesState(MessagesState):\n",
    "    # Add any keys needed beyond messages, which is pre-built \n",
    "    added_key_1: str\n",
    "    added_key_2: str\n",
    "    # etc"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "287805e4-722a-4428-b040-2892b29de870",
   "metadata": {},
   "source": [
    "Let's talk a bit more about usage of the `add_messages` reducer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "c8f61350-4fe0-4a2b-bb24-9305afb3c668",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='Hello! How can I assist you?', name='Model', id='f470d868-cf1b-45b2-ae16-48154cd55c12'),\n",
       " HumanMessage(content=\"I'm looking for information on marine biology.\", name='Lance', id='a07a88c5-cb2a-4cbd-9485-5edb9d658366'),\n",
       " AIMessage(content='Sure, I can help with that. What specifically are you interested in?', name='Model', id='7938e615-86c2-4cbb-944b-c9b2342dee68')]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langgraph.graph.message import add_messages\n",
    "from langchain_core.messages import AIMessage, HumanMessage\n",
    "\n",
    "# Initial state\n",
    "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\"),\n",
    "                    HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\")\n",
    "                   ]\n",
    "\n",
    "# New message to add\n",
    "new_message = AIMessage(content=\"Sure, I can help with that. What specifically are you interested in?\", name=\"Model\")\n",
    "\n",
    "# Test\n",
    "add_messages(initial_messages , new_message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc492370-0502-43e6-87cc-181c60b3dbdb",
   "metadata": {},
   "source": [
    "So we can see that `add_messages` allows us to append messages to the `messages` key in our state.\n",
    "\n",
    "### Re-writing\n",
    "\n",
    "Let's show some useful tricks when working with the `add_messages` reducer.\n",
    "\n",
    "If we pass a message with the same ID as an existing one in our `messages` list, it will get overwritten!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "1f6f82fd-a5a8-4e98-80f6-bb058f2acc47",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='Hello! How can I assist you?', name='Model', id='1'),\n",
       " HumanMessage(content=\"I'm looking for information on whales, specifically\", name='Lance', id='2')]"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Initial state\n",
    "initial_messages = [AIMessage(content=\"Hello! How can I assist you?\", name=\"Model\", id=\"1\"),\n",
    "                    HumanMessage(content=\"I'm looking for information on marine biology.\", name=\"Lance\", id=\"2\")\n",
    "                   ]\n",
    "\n",
    "# New message to add\n",
    "new_message = HumanMessage(content=\"I'm looking for information on whales, specifically\", name=\"Lance\", id=\"2\")\n",
    "\n",
    "# Test\n",
    "add_messages(initial_messages , new_message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f06e7788-7054-4752-99fe-27ebb901f263",
   "metadata": {},
   "source": [
    "### Removal\n",
    "\n",
    "`add_messages` also [enables message removal](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages/). \n",
    "\n",
    "For this, we simply use [RemoveMessage](https://api.python.langchain.com/en/latest/messages/langchain_core.messages.modifier.RemoveMessage.html) from `langchain_core`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "67ac97e5-efe2-40bc-9fe3-fd4f50922b8b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[RemoveMessage(content='', id='1'), RemoveMessage(content='', id='2')]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/l9/bpjxdmfx7lvd1fbdjn38y5dh0000gn/T/ipykernel_17703/3097054180.py:10: LangChainBetaWarning: The class `RemoveMessage` is in beta. It is actively being worked on, so the API may change.\n",
      "  delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import RemoveMessage\n",
    "\n",
    "# Message list\n",
    "messages = [AIMessage(\"Hi.\", name=\"Bot\", id=\"1\")]\n",
    "messages.append(HumanMessage(\"Hi.\", name=\"Lance\", id=\"2\"))\n",
    "messages.append(AIMessage(\"So you said you were researching ocean mammals?\", name=\"Bot\", id=\"3\"))\n",
    "messages.append(HumanMessage(\"Yes, I know about whales. But what others should I learn about?\", name=\"Lance\", id=\"4\"))\n",
    "\n",
    "# Isolate messages to delete\n",
    "delete_messages = [RemoveMessage(id=m.id) for m in messages[:-2]]\n",
    "print(delete_messages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "2d250578-3ec0-452e-91c0-072d785d96db",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='So you said you were researching ocean mammals?', name='Bot', id='3'),\n",
       " HumanMessage(content='Yes, I know about whales. But what others should I learn about?', name='Lance', id='4')]"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "add_messages(messages , delete_messages)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5db095c5-6d9a-4e62-a097-0403797511f6",
   "metadata": {},
   "source": [
    "We can see that mesage IDs 1 and 2, as noted in `delete_messages` are removed by the reducer.\n",
    "\n",
    "We'll see this put into practice a bit later."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c8b0347d-cbf0-4164-9cf6-39c4e040a313",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
